package com.xiaoyun.seleniumlinux.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.MD5;
import com.microsoft.playwright.Browser;
import com.microsoft.playwright.BrowserType;
import com.microsoft.playwright.Page;
import com.microsoft.playwright.Playwright;
import com.xiaoyun.seleniumlinux.config.cache.CookieCache;
import com.xiaoyun.seleniumlinux.enums.HtmlStatType;
import com.xiaoyun.seleniumlinux.mapper.HtmlInfoMapper;
import com.xiaoyun.seleniumlinux.pojo.HtmlInfoDO;
import com.xiaoyun.seleniumlinux.service.WebDiverServer;
import lombok.extern.slf4j.Slf4j;
import org.openqa.selenium.Cookie;
import org.openqa.selenium.WebDriver;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.List;
import java.util.Objects;
import java.util.Set;

/**
 * @author Xiaoyun461
 * @data 2022/5/18
 */
@Service
@Slf4j
public class WebDiverServerImpl implements WebDiverServer {

    /**
     * 时区 - 默认
     */
    public static final String TIME_ZONE_DEFAULT = "GMT+8";
    /**
     * 默认时间 ZoneId
     */
    public static final ZoneId ZONE_ID_DEFAULT = ZoneId.of(TIME_ZONE_DEFAULT);
    /**
     * md5 初始化
     */
    private static final MD5 WEB_DIVER_MD5 = MD5.create();


    @Autowired
    private HtmlInfoMapper htmlInfoMapper;

    @Resource
    private CookieCache cookieCache;


    @Override
    public void seleniumHtml(String url, String unitId, WebDriver webDriver, LocalDate nowTime) {
        HtmlInfoDO htmlInfoDO = htmlInfoMapper.selectByUrl(url);
        if (Objects.nonNull(htmlInfoDO)) {
            log.info("已查到数据库中有该html，id为：{}，url为：{}", htmlInfoDO.getId(), htmlInfoDO.getHtmlUrl());
            LocalDateTime createTime = htmlInfoDO.getCreateTime();
            if (nowTime.equals(createTime.toLocalDate())) {
                log.info("该页面今日已爬虫:{}", url);
                return;
            }

            executeRequest(url, webDriver);
            String pageSource = webDriver.getPageSource();
            String md5Page = WEB_DIVER_MD5.digestHex16(pageSource);

            checkMd5(url, md5Page, webDriver);

            HtmlInfoDO updateHtmlInfoDO = new HtmlInfoDO();
            updateHtmlInfoDO.setId(htmlInfoDO.getId());
            updateHtmlInfoDO.setCreateTime(LocalDateTime.now(ZONE_ID_DEFAULT));
            if (StrUtil.equals(md5Page, htmlInfoDO.getHtmlMd5())) {
                log.info("爬虫获取的页面与数据库中一致:{}", url);
                htmlInfoMapper.updateById(updateHtmlInfoDO);
            } else {
                updateHtmlInfoDO.setHtmlMd5(md5Page);
                updateHtmlInfoDO.setHtmlPage(pageSource);
                updateHtmlInfoDO.setHtmlUrl(url);
                if (StrUtil.isNotBlank(unitId)) {
                    updateHtmlInfoDO.setUnitId(Convert.toLong(unitId));
                    updateHtmlInfoDO.setType(HtmlStatType.WAIT_UPDATE);
                }
                log.info("正在根据Id更新 id为：{}， unitId为：{}，url为：{}，的数据~", htmlInfoDO.getId(), unitId, url);
                htmlInfoMapper.updateById(updateHtmlInfoDO);
            }
        } else {
            log.info("未查到数据库中有该html，其url为：{}", url);

            executeRequest(url, webDriver);

            String pageSource = webDriver.getPageSource();
            String md5Page = WEB_DIVER_MD5.digestHex16(pageSource);

            checkMd5(url, md5Page, webDriver);

            HtmlInfoDO updateOrInsertHtmlInfoDO = new HtmlInfoDO();
            updateOrInsertHtmlInfoDO.setHtmlMd5(md5Page);
            updateOrInsertHtmlInfoDO.setHtmlUrl(url);
            updateOrInsertHtmlInfoDO.setHtmlPage(pageSource);
            updateOrInsertHtmlInfoDO.setCreateTime(LocalDateTime.now());
            if (StrUtil.isBlank(unitId)) {
                log.info("正在插入数据~");
                htmlInfoMapper.insert(updateOrInsertHtmlInfoDO);
            } else {
                updateOrInsertHtmlInfoDO.setUnitId(Convert.toLong(unitId));
                updateOrInsertHtmlInfoDO.setType(HtmlStatType.WAIT_UPDATE);
                HtmlInfoDO htmlInfoDOByDB = htmlInfoMapper.selectIdByUnitId(unitId);
                if (Objects.nonNull(htmlInfoDOByDB) && Objects.nonNull(htmlInfoDOByDB.getId())) {
                    log.info("其unitId有值为{}，请求url为：{},其数据库中url为：{}", unitId, url, htmlInfoDOByDB.getHtmlUrl());
                    log.info("正在更新Id为：{}，的数据~", htmlInfoDOByDB.getId());
                    updateOrInsertHtmlInfoDO.setId(htmlInfoDOByDB.getId());
                    htmlInfoMapper.updateById(updateOrInsertHtmlInfoDO);
                } else {
                    log.info("正在插入数据~,其unitId有值为{}", unitId);
                    htmlInfoMapper.insert(updateOrInsertHtmlInfoDO);
                }
            }
        }
    }

    @Override
    public void seleniumHtml(String url, String unitId, LocalDate nowTime) {
        HtmlInfoDO htmlInfoDO = htmlInfoMapper.selectByUrl(url);
        if (Objects.nonNull(htmlInfoDO)) {
            log.info("已查到数据库中有该html，id为：{}，url为：{}", htmlInfoDO.getId(), htmlInfoDO.getHtmlUrl());
            LocalDateTime createTime = htmlInfoDO.getCreateTime();
            if (nowTime.equals(createTime.toLocalDate())) {
                log.info("该页面今日已爬虫:{}", url);
                return;
            }

            try (Playwright playwright = Playwright.create()) {
                Browser browser = playwright.chromium().launch(
                        new BrowserType.LaunchOptions()
                                .setChannel("chrome")
                                .setProxy("http://localhost:9910")
                                .setHeadless(true));

                Page page = browser.newPage();
                page.setDefaultTimeout(30_000_000);
                page.navigate(url);
                page.waitForLoadState();
                System.out.println(page.title());
                String pageSource = page.content();


//                executeRequest(url, webDriver);
//                String pageSource = webDriver.getPageSource();
                String md5Page = WEB_DIVER_MD5.digestHex16(pageSource);

                checkMd5(url, md5Page);

                HtmlInfoDO updateHtmlInfoDO = new HtmlInfoDO();
                updateHtmlInfoDO.setId(htmlInfoDO.getId());
                updateHtmlInfoDO.setCreateTime(LocalDateTime.now(ZONE_ID_DEFAULT));

                if (StrUtil.equals(md5Page, htmlInfoDO.getHtmlMd5())) {
                    log.info("爬虫获取的页面与数据库中一致:{}", url);
                    htmlInfoMapper.updateById(updateHtmlInfoDO);
                } else {
                    updateHtmlInfoDO.setHtmlMd5(md5Page);
                    updateHtmlInfoDO.setHtmlPage(pageSource);
                    updateHtmlInfoDO.setHtmlUrl(url);
                    if (StrUtil.isNotBlank(unitId)) {
                        updateHtmlInfoDO.setUnitId(Convert.toLong(unitId));
                        updateHtmlInfoDO.setType(HtmlStatType.WAIT_UPDATE);
                    }
                    log.info("正在根据Id更新 id为：{}， unitId为：{}，url为：{}，的数据~", htmlInfoDO.getId(), unitId, url);
                    htmlInfoMapper.updateById(updateHtmlInfoDO);
                }
            }
        } else {
            log.info("未查到数据库中有该html，其url为：{}", url);

            try (Playwright playwright = Playwright.create()) {
                Browser browser = playwright.chromium().launch(
                        new BrowserType.LaunchOptions()
                                .setChannel("chrome")
                                .setProxy("http://localhost:9910")
                                .setHeadless(true));
                Page page = browser.newPage();
                page.setDefaultTimeout(30_000_000);
                page.navigate(url);
                page.waitForLoadState();
                System.out.println(page.title());
                String pageSource = page.content();

//            executeRequest(url, webDriver);

//            String pageSource = webDriver.getPageSource();
                String md5Page = WEB_DIVER_MD5.digestHex16(pageSource);

                checkMd5(url, md5Page);

                HtmlInfoDO updateOrInsertHtmlInfoDO = new HtmlInfoDO();
                updateOrInsertHtmlInfoDO.setHtmlMd5(md5Page);
                updateOrInsertHtmlInfoDO.setHtmlUrl(url);
                updateOrInsertHtmlInfoDO.setHtmlPage(pageSource);
                updateOrInsertHtmlInfoDO.setCreateTime(LocalDateTime.now());
                if (StrUtil.isBlank(unitId)) {
                    log.info("正在插入数据~");
                    htmlInfoMapper.insert(updateOrInsertHtmlInfoDO);
                } else {
                    updateOrInsertHtmlInfoDO.setUnitId(Convert.toLong(unitId));
                    updateOrInsertHtmlInfoDO.setType(HtmlStatType.WAIT_UPDATE);
                    HtmlInfoDO htmlInfoDOByDB = htmlInfoMapper.selectIdByUnitId(unitId);
                    if (Objects.nonNull(htmlInfoDOByDB) && Objects.nonNull(htmlInfoDOByDB.getId())) {
                        log.info("其unitId有值为{}，请求url为：{},其数据库中url为：{}", unitId, url, htmlInfoDOByDB.getHtmlUrl());
                        log.info("正在更新Id为：{}，的数据~", htmlInfoDOByDB.getId());
                        updateOrInsertHtmlInfoDO.setId(htmlInfoDOByDB.getId());
                        htmlInfoMapper.updateById(updateOrInsertHtmlInfoDO);
                    } else {
                        log.info("正在插入数据~,其unitId有值为{}", unitId);
                        htmlInfoMapper.insert(updateOrInsertHtmlInfoDO);
                    }
                }
            }
        }
    }


    /**
     * 参考 https://gitee.com/ssssssss-team/spider-flow-selenium/blob/master/src/main/java/org/spiderflow/selenium/executor/shape/SeleniumExecutor.java
     * 55824毫秒 44819毫秒
     *
     * @param url
     * @param webDriver
     */
    public void executeRequest(String url, WebDriver webDriver) {
        log.info("正在请求url:{}", url);
        //初始化
        webDriver.get(url);
        //检测是否需要开启 cookie 缓存
        if (!CookieCache.COOKIE_CACHE_FLAG) {
            return;
        }
        Set<Cookie> cookies = cookieCache.getCookie(url);
        if (CollUtil.isEmpty(cookies)) {
            return;
        }
        WebDriver.Options manage = webDriver.manage();
        cookies.forEach(manage::addCookie);
        //携带cookie 再次请求
//        webDriver.get(url);
        //刷新页面
        webDriver.navigate().refresh();
    }

    /**
     * 检测 数据库中是否有相同的md5,然后 对比 ,重新请求 获取数据
     *
     * @param url
     * @param md5Page
     * @param webDriver
     */
    private void checkMd5(String url, String md5Page, WebDriver webDriver) {
        List<HtmlInfoDO> htmlInfoList = htmlInfoMapper.selectLisByMd5(md5Page);
        if (htmlInfoList.size() < 2) {
            return;
        }
        boolean onlyMd5Flag = false;
        for (HtmlInfoDO htmlInfoDO : htmlInfoList) {
            String dbHtmlUrl = htmlInfoDO.getHtmlUrl();
            if (StrUtil.equals(dbHtmlUrl, url)) {
                continue;
            }
            log.info("当前数据库中存在相同md5,正在请求其他相同md5数据 url为：{}", url);
            webDriver.get(dbHtmlUrl);
            String pageSource = webDriver.getPageSource();
            String thisMd5 = WEB_DIVER_MD5.digestHex16(pageSource);
            HtmlInfoDO updateHtmlInfoDO = HtmlInfoDO.builder()
                    .id(htmlInfoDO.getId())
                    .htmlPage(pageSource)
                    .htmlMd5(thisMd5).build();
            htmlInfoMapper.updateById(updateHtmlInfoDO);
            log.info("正在保存 其他相同md5数据 url为：{}", url);
            onlyMd5Flag = true;
        }

        if (onlyMd5Flag) {
            throw new RuntimeException("当前数据库中存在相同的数据~正在重新请求");
        }

    }

    private void checkMd5(String url, String md5Page) {
        List<HtmlInfoDO> htmlInfoList = htmlInfoMapper.selectLisByMd5(md5Page);
        if (htmlInfoList.size() < 2) {
            return;
        }
        boolean onlyMd5Flag = false;
        for (HtmlInfoDO htmlInfoDO : htmlInfoList) {
            String dbHtmlUrl = htmlInfoDO.getHtmlUrl();
            if (StrUtil.equals(dbHtmlUrl, url)) {
                continue;
            }
            log.info("当前数据库中存在相同md5,正在请求其他相同md5数据 url为：{}", url);
            try (Playwright playwright = Playwright.create()) {
                Browser browser = playwright.chromium().launch(
                        new BrowserType.LaunchOptions()
                                .setChannel("chrome")
                                .setProxy("http://localhost:9910")
                                .setHeadless(true));
                Page page = browser.newPage();
                page.setDefaultTimeout(30_000_000);
                page.navigate(url);
                page.waitForLoadState();
                System.out.println(page.title());
                String pageSource = page.content();

//                webDriver.get(dbHtmlUrl);
//                String pageSource = webDriver.getPageSource();
                String thisMd5 = WEB_DIVER_MD5.digestHex16(pageSource);
                HtmlInfoDO updateHtmlInfoDO = HtmlInfoDO.builder()
                        .id(htmlInfoDO.getId())
                        .htmlPage(pageSource)
                        .htmlMd5(thisMd5).build();
                htmlInfoMapper.updateById(updateHtmlInfoDO);
                log.info("正在保存 其他相同md5数据 url为：{}", url);
                onlyMd5Flag = true;
            }
        }

        if (onlyMd5Flag) {
            throw new RuntimeException("当前数据库中存在相同的数据~正在重新请求");
        }

    }


}
